Introduction
Connected Objects are a powerful evolution of Network Objects.
They comes with an enhanced and configurable RMI system.
See the next page to know how to start with connected objects.
Make sure you understood the Network Objects concept in order not to get lost while reading this part.
Some reminders:
- An Engine is a connection in a Linkit Network. It can either be the server or a client connection.
- The linkit's network operates in a star topology. In the diagrams below you'll see arrows directly pointing toward engines. This is to simplify the diagrams as the topology is not important here.
Summary
Roughly, Connected objects are Network Objects that gets handled by an
A
The Features list below will introduce to things you can do with connected objects.
Some features are not or not directly concerned by the contract.
Features
Invocation Flow Control (agreements)
When a method of a connected object gets called, the RMI system will follow it's bound
Shared/Synchronized Objects
class MyWrapper[T](value: T = null) {
def set(t: T): T = {
val last = value
value = t
print(s"Changed value to '$value'")
last
}
def get: T = value
}
In this example, we create a fully synchronized mutable object by specifying in our contract that when the set(T): T
method
is invoked on an engine, the other engines that contains the same wrapper object also gets a call with the right parameters.
For our getter, we just handle it as a regular method, as we are sure that our local value is the same on other engines, we don't want the invocation to get performed on our remote engines.
Therefore, we can simulate a "cloud", a "shared" object being accessed from engines and which internal state is completely synchronized on each side.
Select specific engines and apply simple condition depending on the invocation context
You can define simple constraints on your agreements and then having different invocation flows depending on the initial method execution context:
In this example, we have different invocation flow depending on whether the object on which the method is initially called is the original object or not (in red on the diagram)
class MyObject {
/**
* This method prints a param and return the current JVM version.
* */
def doSomething(param: String): String = {
print(s"invocation called ! ($param)")
System.getProperty("java.version")
}
}
Connected Object owners are the engines that initially posted their objects over the network.
Mirror Objects and Remote Implementation control
This is actually one of the most powerful possibility when using Connected Objects.
A
This way, a Mirroring object can be a trait, interface or an abstract class and the Mirrored object is a remote implementation of the class.
Example :
We want to make an api to our server's database by defining a DAO[T]
trait.
The client only have the DAO trait on his side, and the server has the PlayerDAO
that DAO[Player]
trait DAO[A] {
def get(id: Long): A
def getAll: List[A]
def save(a: A): Unit
def update(a: A): Unit
def delete(a: A): Unit
}
Now DAO[Player]
(Mirroring) object where its implementation is on the server-side (Mirrored).
val playerDAO: DAO[Player] = cache.findObject(0).get //this line retrieves the playerDAO object from server's cacheval player = Player(name="Rico", id=78)playerDAO.save(player)playerDAO.get(id=78) == player //true
Here's what happened over the network :
How does it work ?
The Linkit Framework
This allows the RMI System to handle the object's methods invocation by
When a method is
For
When the DAO object got retrieved at first line, the Linkit Framework followed the contract on server-side that said "Use stub class 'DAO[T]' when other engines access this object if it's a
Know who is the invocation origin
Using the utility method ExecutorEngine.currentEngine
, you can know who is the origin of the execution of a method.
This is actually very powerful combined with the possibility of making
class MyObject {
/**
* This method prints who originally invocated the method.
* */
def printInvoker(): Unit = {
val origin = ExecutorEngine.currentEngine
print(s"invocation called ! this RMI has been triggered from " + origin.identifier)
}
}
Static accesses (Work In Progress)
You can also access to statics of a class and thus define a contract for static methods.
You can open your own StaticAccessor
and apply a custom contract for static methods,
or access to a specific engine's static accessor to call static methods on his side.
Example :
- We want to know the time of the server we are connected on :
val serverDate = new Date(network.serverEngine.statics[System].currentTimeMillis())
To access a class (In this example, we access to the System
class), the class must be allowed by the contract on remote-side.
Some methods can be hided by the contract as well :
network.serverEngine.statics[System].exit(0) //throws HiddenMethodInvocationException.
Apply contract on parameters and return values
Mirror / connect parameters and return values
It's possible to propagate object connection by specifying in a contract that parameter and / or return value of a method should be converted to a connected object.
Example :
We want to develop a game with players evolving on a map.
Here is our Player class :
trait Player {
val name: String
def shoot(): Unit
def hit(damage: Double): Unit
def move(x: Int, y: Int): Unit
}
And our map trait :
trait Map {
@throws[Exception]("if a client already created his player")
def newPlayer(name: String): Player
def getPlayer(name: String): Player //returns the same Player instance as returned by #newPlayer
}
Here is our contract (defined here using the bhv language)
import mygame.Player
import mygame.Map
describe Player {
foreach method enable following broadcast
}
describe Map {
//connect keyword in front of a parameter or return type stipulates that the value must be connected.
newPlayer(String): connect Player
}
On MapImpl
object.
On Map#newPlayer
.
By specifying in our contract that the return value of Map#newPlayer
must be converted to a connected Player,
the client will retrieve a connected instance of Player
.
And as all method invocation of Player
are to be broadcast, Player
objects will be completely synchronized, and then for example,
when a client's player moves or shoots, all other clients, and the server, will be aware that the player changed.
Also Connect field values of connected objects
it's also possible to specify which field of a connected object we want to be connected as well:
describe Example {
connect field1
mirror field2
}